class Vue {
  constructor(options = {}) {
    const { data, render } = options

    this._data = data
    this._render = render

    _proxy(this, `_data`, data)

    observe(data)

    new Watcher(this, this._render, (val) => {
      console.log('cb', val)
    })
  }
}

function _proxy(vm, sourceKey, data) {
  const keys = Object.keys(data)

  keys.forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        console.log('>>>>>>>>>>>>>>>>>_proxy', key)
        return vm[sourceKey][key]
      },
      set(val) {
        vm[sourceKey][key] = val
      }
    })
  })
}

function observe(data) {
  new Observer(data)
}

class Observer {
  constructor(data) {
    this.walk(data)
  }

  walk(data) {
    Object.keys(data).forEach(key => {
      defineReactive(data, key)
    })
  }
}

function defineReactive(obj, key) {
  const dep = new Dep()

  let val = obj[key]

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log('收集依赖', key)
      dep.depend()
      return val
    },
    set(newVal) {
      console.log('派发依赖', key)
      val = newVal
      dep.notify()
    }
  })
}

class Dep {
  constructor() {
    this.id = Dep.uid++
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

  removeSub() {
    const subIndex = this.subs.indexOf(sub)
    this.subs.slice(cusIndex, 1)
  }

  depend() {
    if(Dep.target) {
      console.log('>>>>>>>>>>>>>>>>> depend')
      Dep.target.addDep(this)
    }
  }

  notify() {
    console.log('>>>>>>>>>>>>>>>>> notify')
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

Dep.uid = 0
Dep.target = null

class Watcher {
  constructor(vm, render, cb) {
    this.vm = vm
    this.render = render
    this.cb = cb

    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()

    this.value = this.get()
    this.cb(this.value)
  }

  get() {
    Dep.target = this
    const value = this.render.call(this.vm, this.vm)
    this.cleanupDeps()
    return value
  }

  addDep(dep) {
    console.log('>>>>>>>>>>>>>>>>>>>>>>> addDep')
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      console.log('>>>>>>>>>>>>>>>>>>>>>>> addDep2')
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        console.log('>>>>>>>>>>>>>>>>> addDep3')
        dep.addSub(this)
      }
    }
  }

  update() {
    this.value = this.get()
    this.cb(this.value)
  }

  cleanupDeps() {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
}

const vm = new Vue({
  data: {
    name: 'haha',
    age: '33'
  },
  render(vm) {
    return 'name:' + vm.name + '\nage:' + vm.age
  }
})

vm.name = 'hehe'
vm.age = '34'












